// Copyright (c) 2012-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Raven Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "addrman.h"
#include "test/test_raven.h"
#include <string>
#include <boost/test/unit_test.hpp>
#include "hash.h"
#include "serialize.h"
#include "streams.h"
#include "net.h"
#include "netbase.h"
#include "chainparams.h"
#include "util.h"

class CAddrManSerializationMock : public CAddrMan
{
public:
    virtual void Serialize(CDataStream &s) const = 0;

    //! Ensure that bucket placement is always the same for testing purposes.
    void MakeDeterministic()
    {
        nKey.SetNull();
        insecure_rand = FastRandomContext(true);
    }
};

class CAddrManUncorrupted : public CAddrManSerializationMock
{
public:
    void Serialize(CDataStream &s) const override
    {
        CAddrMan::Serialize(s);
    }
};

class CAddrManCorrupted : public CAddrManSerializationMock
{
public:
    void Serialize(CDataStream &s) const override
    {
        // Produces corrupt output that claims addrman has 20 addrs when it only has one addr.
        unsigned char nVersion = 1;
        s << nVersion;
        s << ((unsigned char) 32);
        s << nKey;
        s << 10; // nNew
        s << 10; // nTried

        int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^(1 << 30);
        s << nUBuckets;

        CService serv;
        Lookup("252.1.1.1", serv, 7777, false);
        CAddress addr = CAddress(serv, NODE_NONE);
        CNetAddr resolved;
        LookupHost("252.2.2.2", resolved, false);
        CAddrInfo info = CAddrInfo(addr, resolved);
        s << info;
    }
};

CDataStream AddrmanToStream(CAddrManSerializationMock &_addrman)
{
    CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
    ssPeersIn << FLATDATA(GetParams().MessageStart());
    ssPeersIn << _addrman;
    std::string str = ssPeersIn.str();
    std::vector<unsigned char> vchData(str.begin(), str.end());
    return CDataStream(vchData, SER_DISK, CLIENT_VERSION);
}

BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup)

    BOOST_AUTO_TEST_CASE(cnode_listen_port_test)
    {
        BOOST_TEST_MESSAGE("Running cNode Listen Port Test");

        // test default
        unsigned short port = GetListenPort();
        BOOST_CHECK(port == GetParams().GetDefaultPort());
        // test set port
        unsigned short altPort = 12345;
        gArgs.SoftSetArg("-port", std::to_string(altPort));
        port = GetListenPort();
        BOOST_CHECK(port == altPort);
    }

    BOOST_AUTO_TEST_CASE(caddrdb_read_test)
    {
        BOOST_TEST_MESSAGE("Running cAddrDB Read Test");

        CAddrManUncorrupted addrmanUncorrupted;
        addrmanUncorrupted.MakeDeterministic();

        CService addr1, addr2, addr3;
        Lookup("250.7.1.1", addr1, 8767, false);
        Lookup("250.7.2.2", addr2, 9999, false);
        Lookup("250.7.3.3", addr3, 9999, false);

        // Add three addresses to new table.
        CService source;
        Lookup("252.5.1.1", source, 8767, false);
        addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source);
        addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source);
        addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source);

        // Test that the de-serialization does not throw an exception.
        CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted);
        bool exceptionThrown = false;
        CAddrMan addrman1;

        BOOST_CHECK(addrman1.size() == 0);
        try
        {
            unsigned char pchMsgTmp[4];
            ssPeers1 >> FLATDATA(pchMsgTmp);
            ssPeers1 >> addrman1;
        } catch (const std::exception &e)
        {
            exceptionThrown = true;
        }

        BOOST_CHECK(addrman1.size() == 3);
        BOOST_CHECK(exceptionThrown == false);

        // Test that CAddrDB::Read creates an addrman with the correct number of addrs.
        CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted);

        CAddrMan addrman2;
        CAddrDB adb;
        BOOST_CHECK(addrman2.size() == 0);
        adb.Read(addrman2, ssPeers2);
        BOOST_CHECK(addrman2.size() == 3);
    }


    BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted_test)
    {
        BOOST_TEST_MESSAGE("Running cAddrDB Read Corrupted Test");

        CAddrManCorrupted addrmanCorrupted;
        addrmanCorrupted.MakeDeterministic();

        // Test that the de-serialization of corrupted addrman throws an exception.
        CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted);
        bool exceptionThrown = false;
        CAddrMan addrman1;
        BOOST_CHECK(addrman1.size() == 0);
        try
        {
            unsigned char pchMsgTmp[4];
            ssPeers1 >> FLATDATA(pchMsgTmp);
            ssPeers1 >> addrman1;
        } catch (const std::exception &e)
        {
            exceptionThrown = true;
        }
        // Even through de-serialization failed addrman is not left in a clean state.
        BOOST_CHECK(addrman1.size() == 1);
        BOOST_CHECK(exceptionThrown);

        // Test that CAddrDB::Read leaves addrman in a clean state if de-serialization fails.
        CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted);

        CAddrMan addrman2;
        CAddrDB adb;
        BOOST_CHECK(addrman2.size() == 0);
        adb.Read(addrman2, ssPeers2);
        BOOST_CHECK(addrman2.size() == 0);
    }

    BOOST_AUTO_TEST_CASE(cnode_simple_test)
    {
        BOOST_TEST_MESSAGE("Running cNode Simple Test");

        SOCKET hSocket = INVALID_SOCKET;
        NodeId id = 0;
        int height = 0;

        in_addr ipv4Addr;
        ipv4Addr.s_addr = 0xa0b0c001;

        CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK);
        std::string pszDest = "";
        bool fInboundIn = false;

        // Test that fFeeler is false by default.
        std::unique_ptr<CNode> pnode1(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, CAddress(), pszDest, fInboundIn));
        BOOST_CHECK(pnode1->fInbound == false);
        BOOST_CHECK(pnode1->fFeeler == false);

        fInboundIn = true;
        std::unique_ptr<CNode> pnode2(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 1, 1, CAddress(), pszDest, fInboundIn));
        BOOST_CHECK(pnode2->fInbound == true);
        BOOST_CHECK(pnode2->fFeeler == false);
    }

BOOST_AUTO_TEST_SUITE_END()
